/*
 * PhraseDictionary.h
 *
 *  Created on: Nov 18, 2012
 *      Author: m
 */

#ifndef PHRASEDICTIONARY_H_
#define PHRASEDICTIONARY_H_

#include <ios>
#include <fstream>
#include "../common_components/FreeBlocksManager.h"
#include "../common_components/FixedSizeBlockFile.h"
#include "../common_components/BitMapFreeSpaceManager.h"
#include "../serializers/PhraseSerializer.h"
#include "Phrase.h"

namespace physical
{

/**
 * Archivo que permite acceso secuencial y aleatorio a frases.
 *  También permite altas, bajas y modificaciones de las mismas
 *
 * @brief Diccionario de Frases
 *
 * @sa Phrase
 *
 * @warning Ojo! Cuando las frases se acceden en forma secuencial,
 *          NO se asegura que se devuelvan ordenadas por id,
 *          ni por orden de inserción ni ningun otro criterio.
 *          Simplemente sirve para recorrer todas las frases.
 *
 * @warning Soporta frases de hasta 253 caracteres en total (frase+autor)
 *
 * @warning Soporta hasta 2047 frases en total
 *
 */
class PhraseDictionary
{
public:

    /**
     * Sub-clase que representa un Registro del archivo
     */
    class Record
    {
    public:

        /**
         * Constructor por Defecto
         */
        Record();

        /**
         * Constructor
         *
         * @param position La posición del registro
         * @param content El contenido del registro
         */
        Record( size_t position, const Phrase& content );

        /**
         * Constructor por copia
         *
         * @param other El otro
         */
        Record( const Record& other );

        /**
         * Destructor
         */
        virtual ~Record();

        /**
         * Devuelve la posición del registro
         *
         * @return La posición del registro
         */
        size_t getPosition() const;

        /**
         * Devuelve el contenido de registro
         *
         * @return Referencia al contenido del registro
         */
        Phrase& get();

        /**
         * Carga un nuevo contenido en el registro
         *
         * Se carga el registro con una copia de newContent
         *
         * @param newContent Nuevo contenido a ser asignado
         */
        void set( const Phrase& newContent );

        /**
         * Operador de asignación
         *
         * @param rhs
         * @return
         */
        Record& operator =( const Record& rhs );

        /**
         * Operador de comparación por igualdad
         *
         * @param other La frase con el que se compara
         * @return true si es igual, false en caso contrario
         */
        bool operator ==( const Record& other ) const;

        /**
         * Operador de comparación por desigualdad
         *
         * @param other La frase con el que se compara
         * @return true si no es igual, false en caso contrario
         */
        bool operator !=( const Record& other ) const;

    protected:
        std::unique_ptr< Phrase > m_content;
        size_t m_position;
    };

    /**
     * Contructor por defecto
     *
     * No se asocia ningún archivo al diccionario
     */
    PhraseDictionary();

    /**
     * Contructor por nombre de archivo
     *
     * Se abre el archivo especificado por filename
     *  en el modo de apertura por defecto ( in | out )
     *
     * @param filename Nombre del archivo de diccionario
     */
    PhraseDictionary( const std::string& filename );

    /**
     * Constructor Completo
     *
     * Se abre el archivo especificado por filename
     *  en el modo de apertura especificado.
     *
     * @param filename Nombre del archivo de diccionario
     * @param openmode Modo de apertura del archivo
     */
    PhraseDictionary( const std::string& filename,
            std::ios_base::openmode openmode );

    /**
     * Destructor
     *
     * Cuando se invoca el destructor se intentará cerrar el archivo
     * asociado.
     */
    virtual ~PhraseDictionary();

    /**
     * Añade una Phrase al diccionario
     *
     * Este método recibe una frase y la guarda en el diccionario,
     *  asignándole una posición dentro del mismo.
     *
     * @param phrase Phrase a añadir en el diccionario
     * @return Posición en la que se guardó la frase
     */
    size_t add( const Phrase &phrase );

    /**
     * Elimina la Phrase en la posicón dada
     *
     * @param position La posición de la frase a eliminar
     */
    void remove( size_t position );

    /**
     * Reemplaza la Phrase en la posición indicada, por la nueva
     *  Phrase pasada por parámetro
     *
     * Si no existe ninguna Phrase en la posición dada se lanza
     *  una excepción tipo: PhraseDoesNotExistException.
     *
     * Si la nueva Phrase a guardar supera la longitud máxima
     *  se lanza una excepción tipo: PhraseIsTooLongException
     *
     * @brief Actualiza una Phrase
     *
     * @sa Phrase
     *
     * @throw PhraseDoesNotExistException
     * @throw PhraseIsTooLongException
     *
     * @param position Posición de la Phrase a actualizar
     * @param phrase Nueva Phrase con la que reemplazar a la anterior.
     */
    void update( size_t position, const Phrase& phrase );

    /**
     * Devuelve un Record con la Phrase contenida en la posición
     *  indicada
     *
     * Si la frase no existe lanza una excepción tipo: PhraseDoesNotExistException
     *
     * @throw PhraseDoesNotExistException
     *
     * @sa Record
     * @sa Phrase
     *
     * @param position Posición de la Phrase a leer
     * @return Un Record a la Phrase contenida (ver la documentación de Record)
     */
    Record get( size_t position );

    /**
     * Devuelve el primer registro del diccionario.
     *
     * Este es un método para acceso secuencial al diccionario
     *
     * @sa Record
     *
     * @throw DictionaryIsEmptyException
     *
     * @return Un Record al primer registro del diccionario.
     */
    Record getFirst();

    /**
     * Devuelve el Record posterior al dado.
     *
     * Este es un método para acceso secuencial al diccionario
     *
     * Si no existe un Record siguiente al dado, lanza una
     *  excepción tipo: PhraseDoesNotExistException
     *
     * @sa Record
     *
     * @throw PhraseDoesNotExistException
     *
     * @param phrase Record de referencia
     * @return El Record siguiente a la Phrase de referencia.
     */
    Record getNext( const Record& record );

    /**
     * Indica si existe un Record siguiente al dado.
     *
     * Este es un método para acceso secuencial al diccionario
     *
     * @sa Record
     *
     * @param record Record de referencia
     * @return true si existe un Record siguiente al dado,
     *  false en caso contrario
     */
    bool hasNext( const Record& record );

    /**
     * Abre el archivo de diccionario de frases
     *
     * @param filename Nombre del archivo de diccionario de frases
     * @param openmode Modo de Apertura del archivo
     */
    void open( const std::string &filename, std::ios_base::openmode openmode = s_defaultOpenmode );

    /**
     * Cierra el archivo diccionario de frases
     */
    void close();

private:
    static const std::ios_base::openmode s_defaultOpenmode; /// Modo default de apertura (in|out)
    static const size_t s_blockSize;                        /// Tamaño de bloque

    // TODO: cambiar FixedSizeBlockFile por interfaz: ArchivoDeAccesoAleatorio
    std::unique_ptr< FixedSizeBlockFile > m_file;             /// Archivo en el que se persisten las frases
    std::unique_ptr< FreeBlocksManager >  m_freeSpaceManager; /// Manager de espacio libre
    std::unique_ptr< Serializer<Phrase> > m_phraseSerializer;  /// Serializador de frases

    Record loadRecord( size_t pos );
    void saveRecord( const Record& record );
    bool validatePhraseSize( const Phrase& phrase ) const;
    bool recordExists( size_t pos );
    void freeRecord( size_t pos );
    size_t getFreePosition();
    size_t getNextRecordPosition( size_t pos );
    void writeControlData();
    void readControlData();
    void initializeControlData();
    bool fileExists( const std::string &filename );
};


/*************************************************************
 **********************   Exepciones   **********************
 *************************************************************/

/**
 * Excepción Genérica de Phrase Dictionary
 */
class PhraseDictionaryException : public PhysicalException
{
};

/**
 * Excepción: "La frase no existe!"
 */
class PhraseDoesNotExistException : public PhraseDictionaryException
{
    virtual const char* what() const throw()
    {
        return "Phrase Does Not Exists.";
    }
};

/**
 * Excepción: "La frase es muy larga."
 */
class PhraseIsTooLongException : public PhraseDictionaryException
{
    virtual const char* what() const throw()
    {
        return "Phrase is too long ( max size supported: 255 ).";
    }
};

/**
 * Excepción: "El Diccionario está vacio."
 */
class DictionaryIsEmptyException : public PhraseDictionaryException
{
    virtual const char* what() const throw()
    {
        return "The Dictionary is empty.";
    }
};

/*
 * Métodos de PhraseDictionary::Record
 */

inline PhraseDictionary::Record::Record() :
        m_content( new Phrase() ),
        m_position( 0 )
{
}

inline PhraseDictionary::Record::Record( size_t position,
        const Phrase& content ) :
        m_content( new Phrase( content ) ),
        m_position( position )
{
}

inline PhraseDictionary::Record::Record( const Record& other )
{
    *this = other;
}

inline PhraseDictionary::Record::~Record()
{
}

inline size_t PhraseDictionary::Record::getPosition() const
{
    return m_position;
}

inline Phrase& PhraseDictionary::Record::get()
{
    return *m_content;
}

inline void PhraseDictionary::Record::set( const Phrase& newContent )
{
    m_content.reset( new Phrase( newContent ) );
}

inline PhraseDictionary::Record& PhraseDictionary::Record::operator =( const Record& rhs )
{
    this->m_content.reset( new Phrase( *rhs.m_content ) );
    this->m_position = rhs.m_position;
    return *this;
}

inline bool PhraseDictionary::Record::operator ==( const Record& other ) const
{
    return m_position == other.m_position
            && *this->m_content == *other.m_content;
}

inline bool PhraseDictionary::Record::operator !=( const Record& other ) const
{
    return !( *this == other );
}

} /* namespace physical */
#endif /* PHRASEDICTIONARY_H_ */
